/*
 * xlogger.h
 *
 *  Created on: 2009-7-11
 *      Author: fanliangliang
 */

#ifndef XLOGGER_H_
#define XLOGGER_H_

#include <f32file.h>

// S60 sdk platform version detect.
#ifdef PLATFORM_VERSION
#undef PLATFORM_VERSION
#endif

#if !defined(EKA2) // S60 1st and 2nd Edition
	#include <cdbcols.h>
	// Use GPRS_IF_NAME macro to distinguish between S60v1 and S60v2.
	// Yeah, I know it sucks...
	#ifdef GPRS_IF_NAME
		#define PLATFORM_VERSION 10		// S60 1st Edition
	#else
		#define PLATFORM_VERSION 20		// S60 2nd Edition
	#endif
#else // S60 3rd Edition onwards
	#if defined(__S60_50__)
		#define PLATFORM_VERSION 50		// S60 5th Edition
	#elif defined(__SERIES60_3X__)
		#if defined(__SERIES60_30__)
			#define PLATFORM_VERSION 30 // S60 3rd Edition MR
		#elif defined(__SERIES60_31__)
			#define PLATFORM_VERSION 31 // S60 3rd Edition FP1
		#elif defined(__SERIES60_32__)
			#define PLATFORM_VERSION 32 // S60 3rd Edition FP2
		#endif
	#endif
#endif

#ifndef PLATFORM_VERSION
#define PLATFORM_VERSION 0 // Unknown platform version
#endif


// Checks if xlogger is enabled or not.
#ifndef ENABLE_XLOGGER
	// Not specified by client, xlogger is enabled by default in debug builds.
	#if defined(_DEBUG) || defined(__WINS__) || defined(WINS)
		#define HAS_XLOGGER
	#endif
#else
	// Specified by client.
	#if ENABLE_XLOGGER != 0
		#define HAS_XLOGGER
	#endif
#endif


#ifndef _MSC_VER
	#define __XFUNCTION__ __PRETTY_FUNCTION__
#else
	#if _MSC_VER > 1200
		#define __XFUNCTION__ __FUNCTION__
	#else
		#define __XFUNCTION__ "N/A"
	#endif
#endif


#ifdef HAS_XLOGGER

	#define __L(x) _L(x)
	#define __XFILE__ __L(__FILE__)
	#define CONCAT_IMPL(x, y) x##y
	#define CONCAT(x, y) CONCAT_IMPL(x, y)
	#define ANONYMOUS_VARIABLE(x) CONCAT(x, __LINE__)

	#define xtrace_scope(name) TScopeTracer ANONYMOUS_VARIABLE(_tracer_)(name, __XFILE__, __LINE__)
	#define xtrace_function() xtrace_scope(__XFUNCTION__)
	#define XTRACE_FUNCTION xtrace_scope(__XFUNCTION__)

	#define XLOGGER(level) if (TLoggerWrapper logger = CLogger::InstanceFor(level)) ; else logger->SetLogParameters(level, __XFILE__, __LINE__, CLogger::TSentry(logger()))

	#define XASSERT_A(x) XASSERT_IMPL(x, B)
	#define XASSERT_B(x) XASSERT_IMPL(x, A)
	#define XASSERT_IMPL(x, next) XASSERT_A.AddParameter(#x, x).XASSERT_##next

	#define xassert(expr) if (expr) ; else XLOGGER(ELevelDebug).Asserter().LogExpression(#expr, CAsserter::TSentry(logger->Asserter())).XASSERT_A

	#define xdump  XLOGGER(ELevelTrace).HexDumper()
	#define xtrace XLOGGER(ELevelTrace)
	#define xdebug XLOGGER(ELevelDebug)
	#define xinfo  XLOGGER(ELevelInfo)
	#define xwarn  XLOGGER(ELevelWarn)
	#define xerror XLOGGER(ELevelError)
	#define xfatal XLOGGER(ELevelFatal)

#else

	#define xtrace_scope(name)
	#define xtrace_function
	#define XTRACE_FUNCTION

	struct TEmptyAsserter {
		TEmptyAsserter()
		:XASSERT_A(*this)
		,XASSERT_B(*this) {
			
		}
		
		void xpanic(const TDesC&, TInt) {}
		void xleave(TInt) {}
		void xmessage(const TDesC&) {}
		void xmessage(const char* aMessage) {}

		TEmptyAsserter& XASSERT_A;
		TEmptyAsserter& XASSERT_B;
	};

	#define XASSERT_A(x) XASSERT_IMPL(x, B)
	#define XASSERT_B(x) XASSERT_IMPL(x, A)
	#define XASSERT_IMPL(x, next) XASSERT_A.XASSERT_##next
	
	#define xassert(expr) if (false) ; else TEmptyAsserter().XASSERT_A

	#define xdump  _empty_hexdumper
	#define xtrace _empty_xlogger
	#define xdebug _empty_xlogger
	#define xinfo  _empty_xlogger
	#define xwarn  _empty_xlogger
	#define xerror _empty_xlogger
	#define xfatal _empty_xlogger

	#define _empty_hexdumper if (false) ; else TEmptyHexDumper()
	#define _empty_xlogger if (false) ; else TEmptyXLogger()
	
	struct TEmptyHexDumper {
		void operator()(const TDesC8&) {}
		void operator()(const TDesC16&) {}
		void operator()(const TAny*, TInt) {}
		
		void operator()(const TDesC8&, const char*, ...) {}
		void operator()(const TDesC16&, const char*, ...) {}
		void operator()(const TAny*, TInt, const char*, ...) {}
	};
	
	struct TEmptyXLogger {
		template <typename T>
		TEmptyXLogger& operator<<(const T&) {
			return *this;
		}
		
		void operator()(const char*, ...) {}
	};
	
#endif

	
// Used to indicate a severity level for selecting which logging messages to report.
// The logging levels are in increasing order of severity.
enum TLogLevel {
	ELevelTrace, // Most detailed information, for verbose debugging.
	ELevelDebug, // Detailed information on the flow through the system.
	ELevelInfo,  // Interesting runtime events (startup/shutdown), should be conservative and keep to a minimum.
	ELevelWarn,  // Other runtime situations that are undesirable or unexpected, but not necessarily "wrong".
	ELevelError, // Other runtime errors or unexpected conditions.
	ELevelFatal, // Severe errors that cause premature termination.
	ELevelNone   // Special level used to disable all log messages.
};


class CLogBuffer : public CBase, public TDesOverflow {
public:
	static CLogBuffer* NewL();
	static CLogBuffer* NewLC();
	~CLogBuffer();
	
	void AppendFormat(TRefByValue<const TDesC> aFormat, ...);
	void AppendFormatList(const TDesC& aFormat, VA_LIST aList);
	void Format(TRefByValue<const TDesC> aFormat, ...);
	void FormatList(const TDesC& aFormat, VA_LIST aList);
	
	void Append(bool aValue);
	void Append(char aValue);
	void Append(signed char aValue);
	void Append(unsigned char aValue);
	void Append(short aValue);
	void Append(unsigned short aValue);
	void Append(int aValue);
	void Append(unsigned int aValue);
	void Append(long aValue);
	void Append(unsigned long aValue);
	void Append(float aValue);
	void Append(double aValue);
	void Append(long double aValue);
	void Append(const void* aValue);
	void Append(const char* aValue);
	void Append(TChar aValue);
	void Append(TInt64 aValue);
	void Append(const TDesC16& aDes);
	void Append(const TDesC16* aValue);
	void Append(const TDesC8& aDes);
	void Append(const TDesC8* aValue);
	
	TPtrC8 PtrC8() const;
	const TDesC& DesC() const;
	TInt Length() const;
	void Zero();
	
	// from TDesOverflow
	virtual void Overflow(TDes& /*aDes*/);
	
private:
	CLogBuffer();	
	void ConstructL();
	
private:
	enum {
		KInitialBufferSize = 1024,
		KIncrementalBufferSize = 1024
	};

	HBufC* iBuffer;
	bool iIsOverflowed;
};


// A class for log records.
// This class will be passed to MLogAppenders whenever a log record is fired
// and contains all the pertinent information to the record.
class CLogRecord : public CBase {
public:
	static CLogRecord* NewL();
	static CLogRecord* NewLC();
	~CLogRecord();
	
private:
	CLogRecord();
	void ConstructL();
	
public:
	TLogLevel iLevel;	// The level of this log record.
	TTime iTime;		// The timestamp of this log record.
	CLogBuffer* iMessage;// The message to be logged.
	TFileName iFilename;// The source code file name of this log record.
	TInt iLine;			// The source code line number of this log record.
};


// A MLogAppender represents a device where the log events will be written to.
class MLogAppender {
public:
	// Virtual destructor.
	virtual ~MLogAppender() {}
	
	// Overwrite this method to do actual work when a log record is fired.
	virtual void DoAppendL(const CLogRecord& e) = 0;
};


class CLogAppenderBase : public MLogAppender {
protected:
	CLogAppenderBase();
	virtual ~CLogAppenderBase();
	virtual void DoAppendL(const CLogRecord& e);
	virtual void DoAppendL(const CLogBuffer& iBuffer) = 0;
	
protected:
	void BaseConstructL();
	
protected:
	CLogBuffer* iBuffer;
};


// A CFileAppender write the log records to a file.
class CFileAppender : public CLogAppenderBase {
public:
	static CFileAppender* NewL(const TDesC& aFilename, TUint aFileMode = EFileWrite | EFileStreamText);
	static CFileAppender* NewLC(const TDesC& aFilename, TUint aFileMode = EFileWrite | EFileStreamText);
	virtual ~CFileAppender();
	
	virtual void DoAppendL(const CLogBuffer& iBuffer);
	
private:
	CFileAppender();
	void ConstructL(const TDesC& aFilename, TUint aFileMode);

private:
	RFs iFs;
	RFile iFile;
};


// A CDebugAppender write the log records to carbide.c++ debug view.
class CDebugAppender : public CLogAppenderBase {
public:
	static CDebugAppender* NewL();
	static CDebugAppender* NewLC();
	virtual ~CDebugAppender();
	
	virtual void DoAppendL(const CLogBuffer& iBuffer);
	
private:
	CDebugAppender();
	void ConstructL();
};


// A CConsoleAppender write the log records to a console.
class CConsoleAppender : public CLogAppenderBase {
public:
	static CConsoleAppender* NewL(const TDesC& aTitle);
	static CConsoleAppender* NewL(const TDesC& aTitle, TSize aSize);
	static CConsoleAppender* NewLC(const TDesC& aTitle);
	static CConsoleAppender* NewLC(const TDesC& aTitle, TSize aSize);
	virtual ~CConsoleAppender();
	
	virtual void DoAppendL(const CLogBuffer& iBuffer);
	
private:
	CConsoleAppender();
	void ConstructL(const TDesC& aTitle);
	void ConstructL(const TDesC& aTitle, TSize aSize);

private:
	CConsoleBase* iConsole;
};


class CLogger;
class CAsserter {
public:
	class TSentry {
	public:
		TSentry(CAsserter& aAsserter)
		:iAsserter(aAsserter) {
			
		}
		
		~TSentry() {
			iAsserter.Commit();
		}
	private:
		CAsserter& iAsserter;
	};

	friend class TSentry;
	
	template <typename T>
	friend CAsserter& AddParameterHelper(CAsserter& aAsserter, const char* aName, const TDesC& aFormat, const T& aValue);
	
public:
	static CAsserter* NewL(CLogger& aLogger);
	static CAsserter* NewLC(CLogger& aLogger);
	~CAsserter();

	CAsserter& LogExpression(const char* aExpression, const TSentry& aSentry);
	CAsserter& AddParameter(const char* aName, bool aValue);
	CAsserter& AddParameter(const char* aName, char aValue);
	CAsserter& AddParameter(const char* aName, TChar aValue);
	CAsserter& AddParameter(const char* aName, TInt32 aValue);
	CAsserter& AddParameter(const char* aName, TUint32 aValue);
	CAsserter& AddParameter(const char* aName, TInt64 aValue);
	CAsserter& AddParameter(const char* aName, TReal32 aValue);
	CAsserter& AddParameter(const char* aName, TReal64 aValue);
	CAsserter& AddParameter(const char* aName, const void* aValue);
	CAsserter& AddParameter(const char* aName, const char* aValue);
	CAsserter& AddParameter(const char* aName, const TDesC16& aValue);
	CAsserter& AddParameter(const char* aName, const TDesC16* aValue);
	CAsserter& AddParameter(const char* aName, const TDesC8& aValue);
	CAsserter& AddParameter(const char* aName, const TDesC8* aValue);
	CAsserter& AddParameter(const char* aName, const TPoint& aValue);
	CAsserter& AddParameter(const char* aName, const TSize& aValue);
	CAsserter& AddParameter(const char* aName, const TRect& aValue);
	CAsserter& AddParameter(const char* aName, const TTime& aValue);
	CAsserter& AddParameter(const char* aName, const TDateTime& aValue);

	void xpanic(const TDesC& aCategory, TInt aReason);
	void xleave(TInt aReason);
	void xmessage(const TDesC& aMessage);
	void xmessage(const char* aMessage);
	
private:
	CAsserter& AddDescriptorParameter(const char* aName, const TDesC16& aValue);
	CAsserter& AddOtherParameter(const char* aName, const TDesC16& aValue);
	CAsserter& AddParameterImplement(const char* aName, const TDesC16& aFormat, const TDesC& aValue);
	void Commit();
	void Commit(const TDesC& aMessage);
	
private:
	CAsserter(CLogger& aLogger);
	void ConstructL();
	
public:
	CAsserter& XASSERT_A;
	CAsserter& XASSERT_B;
	
private:
	CLogger& iLogger;
	CLogBuffer* iBuffer;
};


class CHexDumper {
public:
	static CHexDumper* NewL(CLogger& aLogger);
	static CHexDumper* NewLC(CLogger& aLogger);
	~CHexDumper();

	void operator()(const TDesC8& aData);
	void operator()(const TDesC16& aData);
	void operator()(const TAny* aData, TInt aSize);
	
	void operator()(const TDesC8& aData, const char* aFormat, ...);
	void operator()(const TDesC16& aData, const char* aFormat, ...);
	void operator()(const TAny* aData, TInt aSize, const char* aFormat, ...);
	
private:
	CHexDumper(CLogger& aLogger);
	void ConstructL();
	void DumpHeader();
	void DumpLine(TInt aLine, const TInt8* aData, TInt aSize);
	void DumpData(const TAny* aData, TInt aSize);
	void Dump(const TAny* aData, TInt aSize, const char* aFormat, VA_LIST aList);
	
private:
	enum {
		KHeaderBufferSize = 60,
		KLineBufferSize = 78
	};
	
	CLogger& iLogger;
	CLogBuffer* iBuffer;
	TBuf<KHeaderBufferSize> iHeaderBuffer;
	TBuf<KLineBufferSize> iLineBuffer;
};


class CCnvCharacterSetConverter;
class CLogger : public CBase {
	typedef void (*TLoggerInitializer)(CLogger*);
public:
	class TSentry {
	public:
		TSentry(CLogger& aLogger)
		:iLogger(aLogger) {
			
		}
		
		~TSentry() {
			iLogger.Commit();
		}
	private:
		CLogger& iLogger;
	};

	friend class TSentry;
	
public:
	static CLogger* Instance();
	static CLogger* InstanceFor(TLogLevel aLevel);
	
#ifdef HAS_XLOGGER
	static void InitializeOnce(TLoggerInitializer aInitializer = NULL) {
		InitializeOnceImplement(aInitializer);
	}
	
	static void DestroyOnce() {
		DestroyOnceImplement();
	}
#else
	static void InitializeOnce(TLoggerInitializer aInitializer = NULL) {

	}
	
	static void DestroyOnce() {

	}
#endif

	static void DefaultInitializer(CLogger* aLogger);

	CLogger& SetLogParameters(TLogLevel aLevel, const TDesC& aFile, TInt aLine, const TSentry& aSentry);
	CLogger& SetLogParameters(TLogLevel aLevel, const TDesC& aFile, TInt aLine);
	
	HBufC16* ConvertToUnicode(const TDesC8& aForeign);
	HBufC16* ConvertToUnicode(const char* aForeign);
	
	void Log(const char* aFormat, VA_LIST aList);
	void Log(const char* aFormat, ...);
	void operator()(const char* aFormat, ...);
	
	void AddAppender(MLogAppender* aAppender);
	void SetLevel(TLogLevel aLevel);
	TLogLevel Level() const;
	bool IsEnabledFor(TLogLevel aLevel) const;
	
	CAsserter& Asserter();
	CHexDumper& HexDumper();
	
	template <typename T>
	CLogger& operator<<(const T& aValue) {
		iRecord->iMessage->Append(aValue);
		return *this;
	}

private:
	static bool IsInitialized();
	static void InitializeOnceImplement(TLoggerInitializer aInitializer);
	static void DestroyOnceImplement();
	static void Destroy();
	static CLogger* NewL();
	static CLogger* NewLC();
	CLogger();
	~CLogger();
	void ConstructL();
	void Commit();
	void DoAppend();
	void DoAppendL();
	
private:
	TLogLevel iLevel;
	CLogRecord* iRecord;
	CAsserter* iAsserter;
	CHexDumper* iHexDumper;
	CCnvCharacterSetConverter* iConverter;
	RFs iFs;
	RPointerArray<MLogAppender> iAppenders;
	TInt iState;
	TInt iInitializeCount;
	bool iCoutStyle;
};


// A CLogger wrapper class which use some tricks to avoid
// unnecessary function call overhead associate with CLogger::Instance().
class TLoggerWrapper {
public:
	TLoggerWrapper(CLogger* aLogger)
	:iLogger(aLogger) {

	}

	operator bool() const {
		return iLogger == NULL;
	}
	
	CLogger* operator->() const {
		return iLogger;
	}
	
	CLogger& operator()() const {
		return *iLogger;
	}
	
private:
	CLogger* iLogger;
};


// A simple scope tracer that writes the scope Enter, Exit and Leave to xlogger.
class TScopeTracer {
public:
	// Use constructor to write log of entering a scope.
	TScopeTracer(const char* aScopeName, const TDesC& aFile, TInt aLine);
	
	// Use destructor to write log of exiting a scope normally or with a leave.
	~TScopeTracer();
	
private:
	void Cleanup();
	static void OnCleanup(TAny* aObject);

private:
	CLogger* iLogger;
	HBufC* iScopeName;
	HBufC* iFile;
	TInt iLine;
	bool iIsLeaved;
	bool iIsCleanedup;
};

#endif /* XLOGGER_H_ */
